From 15b3c0f563d73787c574d1d03d627fbc46db59c4 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 1 Nov 2019 11:12:21 -0700 Subject: [PATCH] textview: add undo/redo support to GtkTextView This builds upon the GtkTextHistory helper to provide undo and redo support for the GtkTextView widget and GtkTextBuffer object. You can undo/redo using familiar shortcuts such as Primary+Z, Primary+Shift+Z, ad Primary+Y. Developers that wish to disable undo, should set the GtkTextBuffer:enable-undo property to FALSE. You can wrap irreversible actions gtk_text_buffer_begin_irreversible_action() and gtk_text_buffer_end_irreversible_action(). This will cause the undo stack to drop all undo/redo actions and the changes made between them will be the "initial state" of the buffer. Calling gtk_text_buffer_set_text() will do this automatically for you. --- docs/reference/gtk/gtk4-sections.txt | 12 + docs/reference/gtk/text_widget.sgml | 10 + gtk/gtktextbuffer.c | 426 ++++++++++++++++++++++++++- gtk/gtktextbuffer.h | 29 +- gtk/gtktextprivate.h | 2 + gtk/gtktextview.c | 40 +++ 6 files changed, 509 insertions(+), 10 deletions(-) diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 6a246c8e9a..1781176a9e 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -2876,6 +2876,18 @@ gtk_text_buffer_begin_user_action gtk_text_buffer_end_user_action gtk_text_buffer_add_selection_clipboard gtk_text_buffer_remove_selection_clipboard +gtk_text_buffer_get_can_undo +gtk_text_buffer_get_can_redo +gtk_text_buffer_get_enable_undo +gtk_text_buffer_set_enable_undo +gtk_text_buffer_get_max_undo_levels +gtk_text_buffer_set_max_undo_levels +gtk_text_buffer_undo +gtk_text_buffer_redo +gtk_text_buffer_begin_irreversible_action +gtk_text_buffer_end_irreversible_action +gtk_text_buffer_begin_user_action +gtk_text_buffer_end_user_action GTK_TEXT_BUFFER diff --git a/docs/reference/gtk/text_widget.sgml b/docs/reference/gtk/text_widget.sgml index 06caf7386f..e0efd6ccd8 100644 --- a/docs/reference/gtk/text_widget.sgml +++ b/docs/reference/gtk/text_widget.sgml @@ -112,6 +112,16 @@ multiple bytes in UTF-8, and the two-character sequence "\r\n" is also considered a line separator. + +Text buffers support undo and redo if gtk_text_buffer_set_undo_enabled() +has been set to %TRUE. Use gtk_text_buffer_undo() or gtk_text_buffer_redo() +to perform the necessary action. Note that these operations are ignored if +the buffer is not editable. Developers may want some operations to not be +undoable. To do this, wrap your changes in +gtk_text_buffer_begin_irreversible_action() and +gtk_text_buffer_end_irreversible_action(). + + diff --git a/gtk/gtktextbuffer.c b/gtk/gtktextbuffer.c index 5434696670..5ddb1e4b9f 100644 --- a/gtk/gtktextbuffer.c +++ b/gtk/gtktextbuffer.c @@ -30,6 +30,7 @@ #include "gtkdnd.h" #include "gtkmarshalers.h" #include "gtktextbuffer.h" +#include "gtktexthistoryprivate.h" #include "gtktextbufferprivate.h" #include "gtktextbtree.h" #include "gtktextiterprivate.h" @@ -38,6 +39,8 @@ #include "gtkprivate.h" #include "gtkintl.h" +#define DEFAULT_MAX_UNDO 200 + /** * SECTION:gtktextbuffer * @Short_description: Stores attributed text for display in a GtkTextView @@ -62,11 +65,15 @@ struct _GtkTextBufferPrivate GtkTextLogAttrCache *log_attr_cache; + GtkTextHistory *history; + guint user_action_count; /* Whether the buffer has been modified since last save */ guint modified : 1; guint has_selection : 1; + guint can_undo : 1; + guint can_redo : 1; }; typedef struct _ClipboardRequest ClipboardRequest; @@ -93,6 +100,8 @@ enum { BEGIN_USER_ACTION, END_USER_ACTION, PASTE_DONE, + UNDO, + REDO, LAST_SIGNAL }; @@ -108,6 +117,9 @@ enum { PROP_CURSOR_POSITION, PROP_COPY_TARGET_LIST, PROP_PASTE_TARGET_LIST, + PROP_CAN_UNDO, + PROP_CAN_REDO, + PROP_ENABLE_UNDO, LAST_PROP }; @@ -138,6 +150,8 @@ static void gtk_text_buffer_real_changed (GtkTextBuffer *buffe static void gtk_text_buffer_real_mark_set (GtkTextBuffer *buffer, const GtkTextIter *iter, GtkTextMark *mark); +static void gtk_text_buffer_real_undo (GtkTextBuffer *buffer); +static void gtk_text_buffer_real_redo (GtkTextBuffer *buffer); static GtkTextBTree* get_btree (GtkTextBuffer *buffer); static void free_log_attr_cache (GtkTextLogAttrCache *cache); @@ -154,6 +168,24 @@ static void gtk_text_buffer_get_property (GObject *object, GValue *value, GParamSpec *pspec); +static void gtk_text_buffer_history_change_state (gpointer funcs_data, + gboolean is_modified, + gboolean can_undo, + gboolean can_redo); +static void gtk_text_buffer_history_insert (gpointer funcs_data, + guint begin, + guint end, + const char *text, + guint len); +static void gtk_text_buffer_history_delete (gpointer funcs_data, + guint begin, + guint end, + const char *expected_text, + guint len); +static void gtk_text_buffer_history_select (gpointer funcs_data, + int selection_insert, + int selection_bound); + static guint signals[LAST_SIGNAL] = { 0 }; static GParamSpec *text_buffer_props[LAST_PROP]; @@ -185,6 +217,13 @@ GType gtk_text_buffer_content_get_type (void) G_GNUC_CONST; G_DEFINE_TYPE (GtkTextBufferContent, gtk_text_buffer_content, GDK_TYPE_CONTENT_PROVIDER) +static GtkTextHistoryFuncs history_funcs = { + gtk_text_buffer_history_change_state, + gtk_text_buffer_history_insert, + gtk_text_buffer_history_delete, + gtk_text_buffer_history_select, +}; + static GdkContentFormats * gtk_text_buffer_content_ref_formats (GdkContentProvider *provider) { @@ -403,6 +442,8 @@ gtk_text_buffer_class_init (GtkTextBufferClass *klass) klass->remove_tag = gtk_text_buffer_real_remove_tag; klass->changed = gtk_text_buffer_real_changed; klass->mark_set = gtk_text_buffer_real_mark_set; + klass->undo = gtk_text_buffer_real_undo; + klass->redo = gtk_text_buffer_real_redo; /* Construct */ text_buffer_props[PROP_TAG_TABLE] = @@ -439,6 +480,45 @@ gtk_text_buffer_class_init (GtkTextBufferClass *klass) FALSE, GTK_PARAM_READABLE); + /** + * GtkTextBuffer:can-undo: + * + * The :can-undo property denotes that the buffer can undo the last + * applied action. + */ + text_buffer_props[PROP_CAN_UNDO] = + g_param_spec_boolean ("can-undo", + P_("Can Undo"), + P_("If the buffer can have the last action undone"), + FALSE, + GTK_PARAM_READABLE); + + /** + * GtkTextBuffer:can-redo: + * + * The :can-redo property denotes that the buffer can reapply the + * last undone action. + */ + text_buffer_props[PROP_CAN_REDO] = + g_param_spec_boolean ("can-redo", + P_("Can Redo"), + P_("If the buffer can have the last undone action reapplied"), + FALSE, + GTK_PARAM_READABLE); + + /** + * GtkTextBuffer:enable-undo: + * + * The :enable-undo property denotes if support for undoing and redoing + * changes to the buffer is allowed. + */ + text_buffer_props[PROP_ENABLE_UNDO] = + g_param_spec_boolean ("enable-undo", + "Enable Undo", + "Enable support for undo and redo in the text view", + TRUE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + /** * GtkTextBuffer:cursor-position: * @@ -840,6 +920,34 @@ gtk_text_buffer_class_init (GtkTextBufferClass *klass) 1, GDK_TYPE_CLIPBOARD); + /** + * GtkTextBuffer::redo: + * @buffer: a #GtkTextBuffer + * + * The "redo" signal is emitted when a request has been made to redo the + * previously undone operation. + */ + signals[REDO] = + g_signal_new (I_("redo"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkTextBufferClass, redo), + NULL, NULL, NULL, G_TYPE_NONE, 0); + + /** + * GtkTextBuffer::undo: + * @buffer: a #GtkTextBuffer + * + * The "undo" signal is emitted when a request has been made to undo the + * previous operation or set of operations that have been grouped together. + */ + signals[UNDO] = + g_signal_new (I_("undo"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkTextBufferClass, undo), + NULL, NULL, NULL, G_TYPE_NONE, 0); + gtk_text_buffer_register_serializers (); } @@ -848,6 +956,9 @@ gtk_text_buffer_init (GtkTextBuffer *buffer) { buffer->priv = gtk_text_buffer_get_instance_private (buffer); buffer->priv->tag_table = NULL; + buffer->priv->history = gtk_text_history_new (&history_funcs, buffer); + + gtk_text_history_set_max_undo_levels (buffer->priv->history, DEFAULT_MAX_UNDO); } static void @@ -891,6 +1002,10 @@ gtk_text_buffer_set_property (GObject *object, switch (prop_id) { + case PROP_ENABLE_UNDO: + gtk_text_buffer_set_enable_undo (text_buffer, g_value_get_boolean (value)); + break; + case PROP_TAG_TABLE: set_table (text_buffer, g_value_get_object (value)); break; @@ -919,6 +1034,10 @@ gtk_text_buffer_get_property (GObject *object, switch (prop_id) { + case PROP_ENABLE_UNDO: + g_value_set_boolean (value, gtk_text_buffer_get_enable_undo (text_buffer)); + break; + case PROP_TAG_TABLE: g_value_set_object (value, get_table (text_buffer)); break; @@ -946,6 +1065,14 @@ gtk_text_buffer_get_property (GObject *object, g_value_set_int (value, gtk_text_iter_get_offset (&iter)); break; + case PROP_CAN_UNDO: + g_value_set_boolean (value, gtk_text_buffer_get_can_undo (text_buffer)); + break; + + case PROP_CAN_REDO: + g_value_set_boolean (value, gtk_text_buffer_get_can_redo (text_buffer)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -981,6 +1108,8 @@ gtk_text_buffer_finalize (GObject *object) remove_all_selection_clipboards (buffer); + g_clear_object (&buffer->priv->history); + if (priv->tag_table) { _gtk_text_tag_table_remove_buffer (priv->tag_table, buffer); @@ -1058,6 +1187,8 @@ gtk_text_buffer_set_text (GtkTextBuffer *buffer, if (len < 0) len = strlen (text); + gtk_text_history_begin_irreversible_action (buffer->priv->history); + gtk_text_buffer_get_bounds (buffer, &start, &end); gtk_text_buffer_delete (buffer, &start, &end); @@ -1067,6 +1198,8 @@ gtk_text_buffer_set_text (GtkTextBuffer *buffer, gtk_text_buffer_get_iter_at_offset (buffer, &start, 0); gtk_text_buffer_insert (buffer, &start, text, len); } + + gtk_text_history_end_irreversible_action (buffer->priv->history); } @@ -1084,6 +1217,11 @@ gtk_text_buffer_real_insert_text (GtkTextBuffer *buffer, g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer)); g_return_if_fail (iter != NULL); + gtk_text_history_text_inserted (buffer->priv->history, + gtk_text_iter_get_offset (iter), + text, + len); + _gtk_text_btree_insert (iter, text, len); g_signal_emit (buffer, signals[CHANGED], 0); @@ -1798,6 +1936,28 @@ gtk_text_buffer_real_delete_range (GtkTextBuffer *buffer, g_return_if_fail (start != NULL); g_return_if_fail (end != NULL); + if (gtk_text_history_get_enabled (buffer->priv->history)) + { + GtkTextIter sel_begin, sel_end; + gchar *text; + + if (gtk_text_buffer_get_selection_bounds (buffer, &sel_begin, &sel_end)) + gtk_text_history_selection_changed (buffer->priv->history, + gtk_text_iter_get_offset (&sel_begin), + gtk_text_iter_get_offset (&sel_end)); + else + gtk_text_history_selection_changed (buffer->priv->history, + gtk_text_iter_get_offset (&sel_begin), + -1); + + text = gtk_text_iter_get_slice (start, end); + gtk_text_history_text_deleted (buffer->priv->history, + gtk_text_iter_get_offset (start), + gtk_text_iter_get_offset (end), + text, -1); + g_free (text); + } + _gtk_text_btree_delete (start, end); /* may have deleted the selection... */ @@ -3274,17 +3434,14 @@ void gtk_text_buffer_set_modified (GtkTextBuffer *buffer, gboolean setting) { - gboolean fixed_setting; - g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer)); - fixed_setting = setting != FALSE; + setting = !!setting; - if (buffer->priv->modified == fixed_setting) - return; - else + if (buffer->priv->modified != setting) { - buffer->priv->modified = fixed_setting; + buffer->priv->modified = setting; + gtk_text_history_modified_changed (buffer->priv->history, setting); g_signal_emit (buffer, signals[MODIFIED_CHANGED], 0); } } @@ -4723,3 +4880,258 @@ gtk_text_buffer_insert_markup (GtkTextBuffer *buffer, pango_attr_list_unref (attributes); g_free (text); } + +static void +gtk_text_buffer_real_undo (GtkTextBuffer *buffer) +{ + if (gtk_text_history_get_can_undo (buffer->priv->history)) + gtk_text_history_undo (buffer->priv->history); +} + +static void +gtk_text_buffer_real_redo (GtkTextBuffer *buffer) +{ + if (gtk_text_history_get_can_redo (buffer->priv->history)) + gtk_text_history_redo (buffer->priv->history); +} + +gboolean +gtk_text_buffer_get_can_undo (GtkTextBuffer *buffer) +{ + g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE); + + return gtk_text_history_get_can_undo (buffer->priv->history); +} + +gboolean +gtk_text_buffer_get_can_redo (GtkTextBuffer *buffer) +{ + g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE); + + return gtk_text_history_get_can_redo (buffer->priv->history); +} + +static void +gtk_text_buffer_history_change_state (gpointer funcs_data, + gboolean is_modified, + gboolean can_undo, + gboolean can_redo) +{ + GtkTextBuffer *buffer = funcs_data; + + if (buffer->priv->can_undo != can_undo) + { + buffer->priv->can_undo = can_undo; + g_object_notify_by_pspec (G_OBJECT (buffer), text_buffer_props[PROP_CAN_UNDO]); + } + + if (buffer->priv->can_redo != can_redo) + { + buffer->priv->can_redo = can_redo; + g_object_notify_by_pspec (G_OBJECT (buffer), text_buffer_props[PROP_CAN_REDO]); + } + + if (buffer->priv->modified != is_modified) + gtk_text_buffer_set_modified (buffer, is_modified); +} + +static void +gtk_text_buffer_history_insert (gpointer funcs_data, + guint begin, + guint end, + const char *text, + guint len) +{ + GtkTextBuffer *buffer = funcs_data; + GtkTextIter iter; + + gtk_text_buffer_get_iter_at_offset (buffer, &iter, begin); + gtk_text_buffer_insert (buffer, &iter, text, len); +} + +static void +gtk_text_buffer_history_delete (gpointer funcs_data, + guint begin, + guint end, + const char *expected_text, + guint len) +{ + GtkTextBuffer *buffer = funcs_data; + GtkTextIter iter; + GtkTextIter end_iter; + + gtk_text_buffer_get_iter_at_offset (buffer, &iter, begin); + gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end); + gtk_text_buffer_delete (buffer, &iter, &end_iter); +} + +static void +gtk_text_buffer_history_select (gpointer funcs_data, + int selection_insert, + int selection_bound) +{ + GtkTextBuffer *buffer = funcs_data; + GtkTextIter insert; + GtkTextIter bound; + + if (selection_insert == -1 || selection_bound == -1) + return; + + gtk_text_buffer_get_iter_at_offset (buffer, &insert, selection_insert); + gtk_text_buffer_get_iter_at_offset (buffer, &bound, selection_bound); + gtk_text_buffer_select_range (buffer, &insert, &bound); +} + +/** + * gtk_text_buffer_undo: + * @buffer: a #GtkTextBuffer + * + * Undoes the last undoable action on the buffer, if there is one. + */ +void +gtk_text_buffer_undo (GtkTextBuffer *buffer) +{ + g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer)); + + if (gtk_text_buffer_get_can_undo (buffer)) + g_signal_emit (buffer, signals[UNDO], 0); +} + +/** + * gtk_text_buffer_redo: + * @buffer: a #GtkTextBuffer + * + * Redoes the next redoable action on the buffer, if there is one. + */ +void +gtk_text_buffer_redo (GtkTextBuffer *buffer) +{ + g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer)); + + if (gtk_text_buffer_get_can_redo (buffer)) + g_signal_emit (buffer, signals[REDO], 0); +} + +/** + * gtk_text_buffer_get_enable_undo: + * @buffer: a #GtkTextBuffer + * + * Gets whether the buffer is saving modifications to the buffer to allow for + * undo and redo actions. + * + * See gtk_text_buffer_begin_irreversible_action() and + * gtk_text_buffer_end_irreversible_action() to create changes to the buffer + * that cannot be undone. + */ +gboolean +gtk_text_buffer_get_enable_undo (GtkTextBuffer *buffer) +{ + g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE); + + return gtk_text_history_get_enabled (buffer->priv->history); +} + +/** + * gtk_text_buffer_set_enable_undo: + * @buffer: a #GtkTextBuffer + * + * Sets whether or not to enable undoable actions in the text buffer. If + * enabled, the user will be able to undo the last number of actions up to + * gtk_text_buffer_get_max_undo_levels(). + * + * See gtk_text_buffer_begin_irreversible_action() and + * gtk_text_buffer_end_irreversible_action() to create changes to the buffer + * that cannot be undone. + */ +void +gtk_text_buffer_set_enable_undo (GtkTextBuffer *buffer, + gboolean enabled) +{ + g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer)); + + if (enabled != gtk_text_history_get_enabled (buffer->priv->history)) + { + gtk_text_history_set_enabled (buffer->priv->history, enabled); + g_object_notify_by_pspec (G_OBJECT (buffer), + text_buffer_props[PROP_ENABLE_UNDO]); + } +} + +/** + * gtk_text_buffer_begin_irreversible_action: + * @self: a #Gtktextbuffer + * + * Denotes the beginning of an action that may not be undone. This will cause + * any previous operations in the undo/redo queue to be cleared. + * + * This should be paired with a call to + * gtk_text_buffer_end_irreversible_action() after the irreversible action + * has completed. + * + * You may nest calls to gtk_text_buffer_begin_irreversible_action() and + * gtk_text_buffer_end_irreversible_action() pairs. + */ +void +gtk_text_buffer_begin_irreversible_action (GtkTextBuffer *buffer) +{ + g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer)); + + gtk_text_history_begin_irreversible_action (buffer->priv->history); +} + +/** + * gtk_text_buffer_end_irreversible_action: + * @self: a #Gtktextbuffer + * + * Denotes the end of an action that may not be undone. This will cause + * any previous operations in the undo/redo queue to be cleared. + * + * This should be called after completing modifications to the text buffer + * after gtk_text_buffer_begin_irreversible_action() was called. + * + * You may nest calls to gtk_text_buffer_begin_irreversible_action() and + * gtk_text_buffer_end_irreversible_action() pairs. + */ +void +gtk_text_buffer_end_irreversible_action (GtkTextBuffer *buffer) +{ + g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer)); + + gtk_text_history_end_irreversible_action (buffer->priv->history); +} + +/** + * gtk_text_buffer_get_max_undo_levels: + * @buffer: a #GtkTextBuffer + * + * Gets the maximum number of undo levels to perform. If 0, unlimited undo + * actions may be performed. Note that this may have a memory usage impact + * as it requires storing an additional copy of the inserted or removed text + * within the text buffer. + */ +guint +gtk_text_buffer_get_max_undo_levels (GtkTextBuffer *buffer) +{ + g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), 0); + + return gtk_text_history_get_max_undo_levels (buffer->priv->history); +} + +/** + * gtk_text_buffer_set_max_undo_levels: + * @buffer: a #GtkTextBuffer + * @max_undo_levels: the maximum number of undo actions to perform + * + * Sets the maximum number of undo levels to perform. If 0, unlimited undo + * actions may be performed. Note that this may have a memory usage impact + * as it requires storing an additional copy of the inserted or removed text + * within the text buffer. + */ +void +gtk_text_buffer_set_max_undo_levels (GtkTextBuffer *buffer, + guint max_undo_levels) +{ + g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer)); + + gtk_text_history_set_max_undo_levels (buffer->priv->history, max_undo_levels); +} diff --git a/gtk/gtktextbuffer.h b/gtk/gtktextbuffer.h index 51668cbb6e..9517077fe6 100644 --- a/gtk/gtktextbuffer.h +++ b/gtk/gtktextbuffer.h @@ -146,6 +146,8 @@ struct _GtkTextBufferClass void (* paste_done) (GtkTextBuffer *buffer, GdkClipboard *clipboard); + void (* undo) (GtkTextBuffer *buffer); + void (* redo) (GtkTextBuffer *buffer); /*< private >*/ @@ -451,11 +453,32 @@ gboolean gtk_text_buffer_delete_selection (GtkTextBuffer *buffer, gboolean interactive, gboolean default_editable); -/* Called to specify atomic user actions, used to implement undo */ GDK_AVAILABLE_IN_ALL -void gtk_text_buffer_begin_user_action (GtkTextBuffer *buffer); +gboolean gtk_text_buffer_get_can_undo (GtkTextBuffer *buffer); GDK_AVAILABLE_IN_ALL -void gtk_text_buffer_end_user_action (GtkTextBuffer *buffer); +gboolean gtk_text_buffer_get_can_redo (GtkTextBuffer *buffer); +GDK_AVAILABLE_IN_ALL +gboolean gtk_text_buffer_get_enable_undo (GtkTextBuffer *buffer); +GDK_AVAILABLE_IN_ALL +void gtk_text_buffer_set_enable_undo (GtkTextBuffer *buffer, + gboolean enable_undo); +GDK_AVAILABLE_IN_ALL +guint gtk_text_buffer_get_max_undo_levels (GtkTextBuffer *buffer); +GDK_AVAILABLE_IN_ALL +void gtk_text_buffer_set_max_undo_levels (GtkTextBuffer *buffer, + guint max_undo_levels); +GDK_AVAILABLE_IN_ALL +void gtk_text_buffer_undo (GtkTextBuffer *buffer); +GDK_AVAILABLE_IN_ALL +void gtk_text_buffer_redo (GtkTextBuffer *buffer); +GDK_AVAILABLE_IN_ALL +void gtk_text_buffer_begin_irreversible_action (GtkTextBuffer *buffer); +GDK_AVAILABLE_IN_ALL +void gtk_text_buffer_end_irreversible_action (GtkTextBuffer *buffer); +GDK_AVAILABLE_IN_ALL +void gtk_text_buffer_begin_user_action (GtkTextBuffer *buffer); +GDK_AVAILABLE_IN_ALL +void gtk_text_buffer_end_user_action (GtkTextBuffer *buffer); G_END_DECLS diff --git a/gtk/gtktextprivate.h b/gtk/gtktextprivate.h index daeed71bce..500389824d 100644 --- a/gtk/gtktextprivate.h +++ b/gtk/gtktextprivate.h @@ -85,6 +85,8 @@ struct _GtkTextClass void (* paste_clipboard) (GtkText *self); void (* toggle_overwrite) (GtkText *self); void (* insert_emoji) (GtkText *self); + void (* undo) (GtkText *self); + void (* redo) (GtkText *self); }; char * gtk_text_get_display_text (GtkText *entry, diff --git a/gtk/gtktextview.c b/gtk/gtktextview.c index 766060aff7..0c1282c011 100644 --- a/gtk/gtktextview.c +++ b/gtk/gtktextview.c @@ -619,6 +619,13 @@ static void gtk_text_view_activate_misc_insert_emoji (GtkWidget *widget, const char *action_name, GVariant *parameter); +static void gtk_text_view_real_undo (GtkWidget *widget, + const gchar *action_name, + GVariant *parameter); +static void gtk_text_view_real_redo (GtkWidget *widget, + const gchar *action_name, + GVariant *parameter); + /* FIXME probably need the focus methods. */ @@ -1358,6 +1365,9 @@ gtk_text_view_class_init (GtkTextViewClass *klass) NULL, G_TYPE_NONE, 0); + gtk_widget_class_install_action (widget_class, "text.undo", NULL, gtk_text_view_real_undo); + gtk_widget_class_install_action (widget_class, "text.redo", NULL, gtk_text_view_real_redo); + /* * Key bindings */ @@ -1550,6 +1560,14 @@ gtk_text_view_class_init (GtkTextViewClass *klass) gtk_binding_entry_add_signal (binding_set, GDK_KEY_Insert, GDK_SHIFT_MASK, "paste-clipboard", 0); + /* Undo/Redo */ + gtk_binding_entry_add_action (binding_set, GDK_KEY_z, GDK_CONTROL_MASK, + "text.undo", NULL); + gtk_binding_entry_add_action (binding_set, GDK_KEY_y, GDK_CONTROL_MASK, + "text.redo", NULL); + gtk_binding_entry_add_action (binding_set, GDK_KEY_z, GDK_CONTROL_MASK | GDK_SHIFT_MASK, + "text.redo", NULL); + /* Overwrite */ gtk_binding_entry_add_signal (binding_set, GDK_KEY_Insert, 0, "toggle-overwrite", 0); @@ -9835,3 +9853,25 @@ gtk_text_view_get_extra_menu (GtkTextView *text_view) return priv->extra_menu; } + +static void +gtk_text_view_real_undo (GtkWidget *widget, + const gchar *action_name, + GVariant *parameters) +{ + GtkTextView *text_view = GTK_TEXT_VIEW (widget); + + if (gtk_text_view_get_editable (text_view)) + gtk_text_buffer_undo (text_view->priv->buffer); +} + +static void +gtk_text_view_real_redo (GtkWidget *widget, + const gchar *action_name, + GVariant *parameters) +{ + GtkTextView *text_view = GTK_TEXT_VIEW (widget); + + if (gtk_text_view_get_editable (text_view)) + gtk_text_buffer_redo (text_view->priv->buffer); +} -- 2.30.2